//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.xa
{


	using DynamicRecord = org.neo4j.kernel.impl.nioneo.store.DynamicRecord;
	using InvalidRecordException = org.neo4j.kernel.impl.nioneo.store.InvalidRecordException;
	using NeoStore = org.neo4j.kernel.impl.nioneo.store.NeoStore;
	using NodeRecord = org.neo4j.kernel.impl.nioneo.store.NodeRecord;
	using NodeStore = org.neo4j.kernel.impl.nioneo.store.NodeStore;
	using PropertyIndexRecord = org.neo4j.kernel.impl.nioneo.store.PropertyIndexRecord;
	using PropertyIndexStore = org.neo4j.kernel.impl.nioneo.store.PropertyIndexStore;
	using PropertyRecord = org.neo4j.kernel.impl.nioneo.store.PropertyRecord;
	using PropertyStore = org.neo4j.kernel.impl.nioneo.store.PropertyStore;
	using PropertyType = org.neo4j.kernel.impl.nioneo.store.PropertyType;
	using Record = org.neo4j.kernel.impl.nioneo.store.Record;
	using RelationshipRecord = org.neo4j.kernel.impl.nioneo.store.RelationshipRecord;
	using RelationshipStore = org.neo4j.kernel.impl.nioneo.store.RelationshipStore;
	using RelationshipTypeRecord = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord;
	using RelationshipTypeStore = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeStore;
	using LogBuffer = org.neo4j.kernel.impl.transaction.xaframework.LogBuffer;
	using XaCommand = org.neo4j.kernel.impl.transaction.xaframework.XaCommand;

///
/// <summary> * Command implementations for all the commands that can be performed on a Neo
/// * store. </summary>
/// 
	internal abstract class Command : XaCommand
	{
		internal static Logger logger = Logger.getLogger(typeof(Command).Name);

		private readonly int key;

		internal Command(int key)
		{
			this.key = key;
		}

		protected internal override void setRecovered()
		{
			base.setRecovered();
		}

		internal virtual int Key
		{
			get
			{
				return key;
			}
		}

		public override int GetHashCode()
		{
			return key;
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: static void writeDynamicRecord(LogBuffer buffer, DynamicRecord record) throws IOException
		internal static void writeDynamicRecord(LogBuffer buffer, DynamicRecord record)
		{
		// id+type+in_use(byte)+prev_block(int)+nr_of_bytes(int)+next_block(int)
			if (record.inUse())
			{
				sbyte inUse = Record.IN_USE.byteValue();
				buffer.putInt(record.getId()).putInt(record.getType()).put(inUse).putInt(record.getPrevBlock()).putInt(record.Length).putInt(record.getNextBlock());
				if (!record.isLight())
				{
					if (!record.isCharData())
					{
						sbyte[] data = record.getData();
						buffer.put(data);
					}
					else
					{
						char[] chars = record.getDataAsChar();
						buffer.put(chars);
					}
				}
			}
			else
			{
				sbyte inUse = Record.NOT_IN_USE.byteValue();
				buffer.putInt(record.getId()).putInt(record.getType()).put(inUse);
			}
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: static DynamicRecord readDynamicRecord(ReadableByteChannel byteChannel, ByteBuffer buffer) throws IOException
		internal static DynamicRecord readDynamicRecord(ReadableByteChannel byteChannel, ByteBuffer buffer)
		{
		// id+type+in_use(byte)+prev_block(int)+nr_of_bytes(int)+next_block(int)
			buffer.Clear();
			buffer.limit(9);
			if (byteChannel.read(buffer) != buffer.limit())
			{
				return null;
			}
			buffer.flip();
			int id = buffer.getInt();
			int type = buffer.getInt();
			sbyte inUseFlag = buffer.get();
			bool inUse = false;
			if (inUseFlag == Record.IN_USE.byteValue())
			{
				inUse = true;
				buffer.Clear();
				buffer.limit(12);
				if (byteChannel.read(buffer) != buffer.limit())
				{
					return null;
				}
				buffer.flip();
			}
			else if (inUseFlag != Record.NOT_IN_USE.byteValue())
			{
				throw new IOException("Illegal in use flag: " + inUseFlag);
			}
			DynamicRecord record = new DynamicRecord(id);
			record.setInUse(inUse, type);
			if (inUse)
			{
				record.setPrevBlock(buffer.getInt());
				int nrOfBytes = buffer.getInt();
				record.setNextBlock(buffer.getInt());
				buffer.Clear();
				buffer.limit(nrOfBytes);
				if (byteChannel.read(buffer) != buffer.limit())
				{
					return null;
				}
				buffer.flip();
				sbyte[] data = new sbyte[nrOfBytes];
				buffer.get(data);
				record.setData(data);
			}
			return record;
		}

	// means the first byte of the command record was only written but second 
	// (saying what type) did not get written but the file still got expanded
		private const sbyte NONE = (sbyte) 0;

		private const sbyte NODE_COMMAND = (sbyte) 1;
		private const sbyte PROP_COMMAND = (sbyte) 2;
		private const sbyte REL_COMMAND = (sbyte) 3;
		private const sbyte REL_TYPE_COMMAND = (sbyte) 4;
		private const sbyte PROP_INDEX_COMMAND = (sbyte) 5;

		internal class NodeCommand : Command
		{
			private readonly NodeRecord record;
			private readonly NodeStore store;

			internal NodeCommand(NodeStore store, NodeRecord record) : base(record.getId())
			{
				this.record = record;
				this.store = store;
			}

			public override void execute()
			{
				if (isRecovered())
				{
					logger.fine(this.ToString());
					store.updateRecord(record, true);
				}
				else
				{
					store.updateRecord(record);
				}
			}

			public override string ToString()
			{
				return "NodeCommand[" + record + "]";
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void writeToFile(LogBuffer buffer) throws IOException
			public override void writeToFile(LogBuffer buffer)
			{
				sbyte inUse = record.inUse() ? Record.IN_USE.byteValue() : Record.NOT_IN_USE.byteValue();
				buffer.put(NODE_COMMAND);
				buffer.putInt(record.getId());
				buffer.put(inUse);
				if (record.inUse())
				{
					buffer.putInt(record.getNextRel()).putInt(record.getNextProp());
				}
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer) throws IOException
			internal static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer)
			{
				buffer.Clear();
				buffer.limit(5);
				if (byteChannel.read(buffer) != buffer.limit())
				{
					return null;
				}
				buffer.flip();
				int id = buffer.getInt();
				sbyte inUseFlag = buffer.get();
				bool inUse = false;
				if (inUseFlag == Record.IN_USE.byteValue())
				{
					inUse = true;
				}
				else if (inUseFlag != Record.NOT_IN_USE.byteValue())
				{
					throw new IOException("Illegal in use flag: " + inUseFlag);
				}
				NodeRecord record = new NodeRecord(id);
				record.setInUse(inUse);
				if (inUse)
				{
					buffer.Clear();
					buffer.limit(8);
					if (byteChannel.read(buffer) != buffer.limit())
					{
						return null;
					}
					buffer.flip();
					record.setNextRel(buffer.getInt());
					record.setNextProp(buffer.getInt());
				}
				return new NodeCommand(neoStore.getNodeStore(), record);
			}

			public override bool Equals(object o)
			{
				if (!(o is NodeCommand))
				{
					return false;
				}
				return Key == ((Command) o).Key;
			}
		}

		internal class RelationshipCommand : Command
		{
			private readonly RelationshipRecord record;
			private readonly RelationshipStore store;

			internal RelationshipCommand(RelationshipStore store, RelationshipRecord record) : base(record.getId())
			{
				this.record = record;
				this.store = store;
			}

			public override void execute()
			{
				if (isRecovered())
				{
					logger.fine(this.ToString());
					store.updateRecord(record, true);
				}
				else
				{
					store.updateRecord(record);
				}
			}

			public override string ToString()
			{
				return "RelationshipCommand[" + record + "]";
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void writeToFile(LogBuffer buffer) throws IOException
			public override void writeToFile(LogBuffer buffer)
			{
				sbyte inUse = record.inUse() ? Record.IN_USE.byteValue() : Record.NOT_IN_USE.byteValue();
				buffer.put(REL_COMMAND);
				buffer.putInt(record.getId());
				buffer.put(inUse);
				if (record.inUse())
				{
					buffer.putInt(record.getFirstNode()).putInt(record.getSecondNode()).putInt(record.getType()).putInt(record.getFirstPrevRel()).putInt(record.getFirstNextRel()).putInt(record.getSecondPrevRel()).putInt(record.getSecondNextRel()).putInt(record.getNextProp());
				}
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer) throws IOException
			internal static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer)
			{
				buffer.Clear();
				buffer.limit(5);
				if (byteChannel.read(buffer) != buffer.limit())
				{
					return null;
				}
				buffer.flip();
				int id = buffer.getInt();
				sbyte inUseFlag = buffer.get();
				bool inUse = false;
				if ((inUseFlag & Record.IN_USE.byteValue()) == Record.IN_USE.byteValue())
				{
					inUse = true;
				}
				else if ((inUseFlag & Record.IN_USE.byteValue()) != Record.NOT_IN_USE.byteValue())
				{
					throw new IOException("Illegal in use flag: " + inUseFlag);
				}
				RelationshipRecord record;
				if (inUse)
				{
					buffer.Clear();
					buffer.limit(32);
					if (byteChannel.read(buffer) != buffer.limit())
					{
						return null;
					}
					buffer.flip();
					record = new RelationshipRecord(id, buffer.getInt(), buffer.getInt(), buffer.getInt());
					record.setInUse(inUse);
					record.setFirstPrevRel(buffer.getInt());
					record.setFirstNextRel(buffer.getInt());
					record.setSecondPrevRel(buffer.getInt());
					record.setSecondNextRel(buffer.getInt());
					record.setNextProp(buffer.getInt());
				}
				else
				{
					record = new RelationshipRecord(id, -1, -1, -1);
					record.setInUse(false);
				}
				return new RelationshipCommand(neoStore.getRelationshipStore(), record);
			}

			public override bool Equals(object o)
			{
				if (!(o is RelationshipCommand))
				{
					return false;
				}
				return Key == ((Command) o).Key;
			}
		}

		internal class PropertyIndexCommand : Command
		{
			private readonly PropertyIndexRecord record;
			private readonly PropertyIndexStore store;

			internal PropertyIndexCommand(PropertyIndexStore store, PropertyIndexRecord record) : base(record.getId())
			{
				this.record = record;
				this.store = store;
			}

			public override void execute()
			{
				if (isRecovered())
				{
					logger.fine(this.ToString());
					store.updateRecord(record, true);
				}
				else
				{
					store.updateRecord(record);
				}
			}

			public override string ToString()
			{
				return "PropertyIndexCommand[" + record + "]";
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void writeToFile(LogBuffer buffer) throws IOException
			public override void writeToFile(LogBuffer buffer)
			{
			// id+in_use(byte)+count(int)+key_blockId(int)+nr_key_records(int)
				sbyte inUse = record.inUse() ? Record.IN_USE.byteValue() : Record.NOT_IN_USE.byteValue();
				buffer.put(PROP_INDEX_COMMAND);
				buffer.putInt(record.getId());
				buffer.put(inUse);
				buffer.putInt(record.getPropertyCount()).putInt(record.getKeyBlockId());
				if (record.isLight())
				{
					buffer.putInt(0);
				}
				else
				{
					Collection<DynamicRecord> keyRecords = record.getKeyRecords();
					buffer.putInt(keyRecords.size());
					foreach (DynamicRecord keyRecord in keyRecords)
					{
						writeDynamicRecord(buffer, keyRecord);
					}
				}
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer) throws IOException
			internal static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer)
			{
			// id+in_use(byte)+count(int)+key_blockId(int)+nr_key_records(int)
				buffer.Clear();
				buffer.limit(17);
				if (byteChannel.read(buffer) != buffer.limit())
				{
					return null;
				}
				buffer.flip();
				int id = buffer.getInt();
				sbyte inUseFlag = buffer.get();
				bool inUse = false;
				if ((inUseFlag & Record.IN_USE.byteValue()) == Record.IN_USE.byteValue())
				{
					inUse = true;
				}
				else if (inUseFlag != Record.NOT_IN_USE.byteValue())
				{
					throw new IOException("Illegal in use flag: " + inUseFlag);
				}
				PropertyIndexRecord record = new PropertyIndexRecord(id);
				record.setInUse(inUse);
				record.setPropertyCount(buffer.getInt());
				record.setKeyBlockId(buffer.getInt());
				int nrKeyRecords = buffer.getInt();
				for (int i = 0; i < nrKeyRecords; i++)
				{
					DynamicRecord dr = readDynamicRecord(byteChannel, buffer);
					if (dr == null)
					{
						return null;
					}
					record.addKeyRecord(dr);
				}
				return new PropertyIndexCommand(neoStore.getPropertyStore().getIndexStore(), record);
			}

			public override bool Equals(object o)
			{
				if (!(o is PropertyIndexCommand))
				{
					return false;
				}
				return Key == ((Command) o).Key;
			}
		}

		internal class PropertyCommand : Command
		{
			private readonly PropertyRecord record;
			private readonly PropertyStore store;

			internal PropertyCommand(PropertyStore store, PropertyRecord record) : base(record.getId())
			{
				this.record = record;
				this.store = store;
			}

			public override void execute()
			{
				if (isRecovered())
				{
					logger.fine(this.ToString());
					store.updateRecord(record, true);
				}
				else
				{
					store.updateRecord(record);
				}
			}

			public override string ToString()
			{
				return "PropertyCommand[" + record + "]";
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void writeToFile(LogBuffer buffer) throws IOException
			public override void writeToFile(LogBuffer buffer)
			{
			// id+in_use(byte)+type(int)+key_indexId(int)+prop_blockId(long)+
			// prev_prop_id(int)+next_prop_id(int)+nr_value_records(int)
				sbyte inUse = record.inUse() ? Record.IN_USE.byteValue() : Record.NOT_IN_USE.byteValue();
				buffer.put(PROP_COMMAND);
				buffer.putInt(record.getId());
				buffer.put(inUse);
				if (record.inUse())
				{
					buffer.putInt(record.getType().intValue()).putInt(record.getKeyIndexId()).putLong(record.getPropBlock()).putInt(record.getPrevProp()).putInt(record.getNextProp());
				}
				if (record.isLight())
				{
					buffer.putInt(0);
				}
				else
				{
					Collection<DynamicRecord> valueRecords = record.getValueRecords();
					buffer.putInt(valueRecords.size());
					foreach (DynamicRecord valueRecord in valueRecords)
					{
						writeDynamicRecord(buffer, valueRecord);
					}
				}
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer) throws IOException
			internal static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer)
			{
			// id+in_use(byte)+type(int)+key_indexId(int)+prop_blockId(long)+
			// prev_prop_id(int)+next_prop_id(int)+nr_value_records(int)
				buffer.Clear();
				buffer.limit(5);
				if (byteChannel.read(buffer) != buffer.limit())
				{
					return null;
				}
				buffer.flip();
				int id = buffer.getInt();
				sbyte inUseFlag = buffer.get();
				bool inUse = false;
				if ((inUseFlag & Record.IN_USE.byteValue()) == Record.IN_USE.byteValue())
				{
					inUse = true;
				}
				else if (inUseFlag != Record.NOT_IN_USE.byteValue())
				{
					throw new IOException("Illegal in use flag: " + inUseFlag);
				}
				PropertyRecord record = new PropertyRecord(id);
				if (inUse)
				{
					buffer.Clear();
					buffer.limit(24);
					if (byteChannel.read(buffer) != buffer.limit())
					{
						return null;
					}
					buffer.flip();
					PropertyType type = getType(buffer.getInt());
					if (type == null)
					{
						return null;
					}
					record.setType(type);
					record.setInUse(inUse);
					record.setKeyIndexId(buffer.getInt());
					record.setPropBlock(buffer.getLong());
					record.setPrevProp(buffer.getInt());
					record.setNextProp(buffer.getInt());
				}
				buffer.Clear();
				buffer.limit(4);
				if (byteChannel.read(buffer) != buffer.limit())
				{
					return null;
				}
				buffer.flip();
				int nrValueRecords = buffer.getInt();
				for (int i = 0; i < nrValueRecords; i++)
				{
					DynamicRecord dr = readDynamicRecord(byteChannel, buffer);
					if (dr == null)
					{
						return null;
					}
					record.addValueRecord(dr);
				}
				return new PropertyCommand(neoStore.getPropertyStore(), record);
			}

			private static PropertyType getType(int type)
			{
				switch (type)
				{
					case 1:
						return PropertyType.INT;
					case 2:
						return PropertyType.STRING;
					case 3:
						return PropertyType.BOOL;
					case 4:
						return PropertyType.DOUBLE;
					case 5:
						return PropertyType.FLOAT;
					case 6:
						return PropertyType.LONG;
					case 7:
						return PropertyType.BYTE;
					case 8:
						return PropertyType.CHAR;
					case 9:
						return PropertyType.ARRAY;
					case 10:
						return PropertyType.SHORT;
					case 0:
						return null;
				}
				throw new InvalidRecordException("Unknown property type:" + type);
			}

			public override bool Equals(object o)
			{
				if (!(o is PropertyCommand))
				{
					return false;
				}
				return Key == ((Command) o).Key;
			}
		}

		internal class RelationshipTypeCommand : Command
		{
			private readonly RelationshipTypeRecord record;
			private readonly RelationshipTypeStore store;

			internal RelationshipTypeCommand(RelationshipTypeStore store, RelationshipTypeRecord record) : base(record.getId())
			{
				this.record = record;
				this.store = store;
			}

			public override void execute()
			{
				if (isRecovered())
				{
					logger.fine(this.ToString());
					store.updateRecord(record, true);
				}
				else
				{
					store.updateRecord(record);
				}
			}

			public override string ToString()
			{
				return "RelationshipTypeCommand[" + record + "]";
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void writeToFile(LogBuffer buffer) throws IOException
			public override void writeToFile(LogBuffer buffer)
			{
			// id+in_use(byte)+type_blockId(int)+nr_type_records(int)
				sbyte inUse = record.inUse() ? Record.IN_USE.byteValue() : Record.NOT_IN_USE.byteValue();
				buffer.put(REL_TYPE_COMMAND);
				buffer.putInt(record.getId()).put(inUse).putInt(record.getTypeBlock());

				Collection<DynamicRecord> typeRecords = record.getTypeRecords();
				buffer.putInt(typeRecords.size());
				foreach (DynamicRecord typeRecord in typeRecords)
				{
					writeDynamicRecord(buffer, typeRecord);
				}
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer) throws IOException
			internal static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer)
			{
			// id+in_use(byte)+type_blockId(int)+nr_type_records(int)
				buffer.Clear();
				buffer.limit(13);
				if (byteChannel.read(buffer) != buffer.limit())
				{
					return null;
				}
				buffer.flip();
				int id = buffer.getInt();
				sbyte inUseFlag = buffer.get();
				bool inUse = false;
				if ((inUseFlag & Record.IN_USE.byteValue()) == Record.IN_USE.byteValue())
				{
					inUse = true;
				}
				else if (inUseFlag != Record.NOT_IN_USE.byteValue())
				{
					throw new IOException("Illegal in use flag: " + inUseFlag);
				}
				RelationshipTypeRecord record = new RelationshipTypeRecord(id);
				record.setInUse(inUse);
				record.setTypeBlock(buffer.getInt());
				int nrTypeRecords = buffer.getInt();
				for (int i = 0; i < nrTypeRecords; i++)
				{
					DynamicRecord dr = readDynamicRecord(byteChannel, buffer);
					if (dr == null)
					{
						return null;
					}
					record.addTypeRecord(dr);
				}
				return new RelationshipTypeCommand(neoStore.getRelationshipTypeStore(), record);
			}

			public override bool Equals(object o)
			{
				if (!(o is RelationshipTypeCommand))
				{
					return false;
				}
				return Key == ((Command) o).Key;
			}
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer) throws IOException
		static Command readCommand(NeoStore neoStore, ReadableByteChannel byteChannel, ByteBuffer buffer)
		{
			buffer.Clear();
			buffer.limit(1);
			if (byteChannel.read(buffer) != buffer.limit())
			{
				return null;
			}
			buffer.flip();
			sbyte commandType = buffer.get();
			switch (commandType)
			{
				case NODE_COMMAND:
					return NodeCommand.readCommand(neoStore, byteChannel, buffer);
				case PROP_COMMAND:
					return PropertyCommand.readCommand(neoStore, byteChannel, buffer);
				case PROP_INDEX_COMMAND:
					return PropertyIndexCommand.readCommand(neoStore, byteChannel, buffer);
				case REL_COMMAND:
					return RelationshipCommand.readCommand(neoStore, byteChannel, buffer);
				case REL_TYPE_COMMAND:
					return RelationshipTypeCommand.readCommand(neoStore, byteChannel, buffer);
				case NONE:
					return null;
				default:
					throw new IOException("Unknown command type[" + commandType + "]");
			}
		}
	}
}